// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ApproximationTests.cs" company="Microsoft Corporation">
// The MIT License (MIT)
// 
// Copyright (c) 2014, Microsoft Corporation
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace Microsoft.Robotics.Core.Algorithms.UnitTests
{
    using System;
    using Microsoft.Robotics.Numerics;
    using Microsoft.VisualStudio.TestTools.UnitTesting;

    /// <summary>
    /// Class for test polynomial approximation utilities
    /// </summary>
    [TestClass]
    public class ApproximationTests
    {
        /// <summary>
        /// Test if Throws When Data Set Is Empty
        /// </summary>
        [Priority(0)]
        [TestMethod]
        [TestCategory("Unit")]
        [ExpectedException(typeof(InvalidOperationException))]
        public void CoefficientsAreNullWhenDataSetIsEmpty()
        {
            PolynomialEstimator solver = new PolynomialEstimator();
            
            FastTuple<double, double> outFit;
            solver.FitALine(out outFit);
        }

        /// <summary>
        /// Test if CoefficientsAreNullWhenInputLengthIsTooSmall
        /// </summary>
        [Priority(0)]
        [TestMethod]
        [TestCategory("Unit")]
        public void CoefficientsAreNullWhenInputLengthIsTooSmall()
        {
            PolynomialEstimator solver = new PolynomialEstimator();

            solver.Add(1, 2);

            FastTuple<double, double> outFit;

            Assert.IsFalse(solver.FitALine(out outFit));
        }

        /// <summary>
        /// Test if CoefficientsAreNullWhenXValuesAreTheSame
        /// </summary>
        [Priority(0)]
        [TestMethod]
        [TestCategory("Unit")]
        public void CoefficientsAreNullWhenXValuesAreTheSame()
        {
            PolynomialEstimator solver = new PolynomialEstimator();

            solver.Add(1, 2);
            solver.Add(1, 2);
            solver.Add(1, 2);

            FastTuple<double, double> outFit;

            Assert.IsFalse(solver.FitALine(out outFit));
        }

        /// <summary>
        /// Test if AIsZeroWhenLineIsParallel
        /// </summary>
        [Priority(0)]
        [TestMethod]
        [TestCategory("Unit")]
        public void AIsZeroWhenLineIsParallel()
        {
            PolynomialEstimator solver = new PolynomialEstimator();

            solver.Add(1, 2);
            solver.Add(2, 2);
            solver.Add(3, 2);

            FastTuple<double, double> outFit;

            bool result = solver.FitALine(out outFit);

            Assert.IsTrue(result);
            Assert.AreEqual(2, outFit.Item1);
            Assert.AreEqual(0, outFit.Item2);
        }

        /// <summary>
        /// Test if Data Points Are Empty After Calling Clear
        /// </summary>
        [Priority(0)]
        [TestMethod]
        [TestCategory("Unit")]
        public void DataPointsAreEmptyAfterCallingClear()
        {
            PolynomialEstimator solver = new PolynomialEstimator();

            solver.Add(1, 1);
            solver.Add(2, 2);
            solver.Add(3, 3);

            solver.Clear();

            FastTuple<double, double> outFit;
            bool result = solver.FitALine(out outFit);

            Assert.IsFalse(result);
            Assert.AreEqual(0, outFit.Item1);
            Assert.AreEqual(0, outFit.Item2);
        }

        /// <summary>
        /// Test if BIsZeroWhenLineIsCrossingZero
        /// </summary>
        [Priority(0)]
        [TestMethod]
        [TestCategory("Unit")]
        public void BIsZeroWhenLineIsCrossingZero()
        {
            PolynomialEstimator solver = new PolynomialEstimator();

            solver.Add(1, 1);
            solver.Add(2, 2);
            solver.Add(3, 3);

            FastTuple<double, double> outFit;

            bool result = solver.FitALine(out outFit);

            Assert.IsTrue(result);
            Assert.AreEqual(0, outFit.Item1);
            Assert.AreEqual(1, outFit.Item2);
        }

        /// <summary>
        /// Test if AAndBAreValidForAShiftedLine
        /// </summary>
        [Priority(0)]
        [TestMethod]
        [TestCategory("Unit")]
        public void AAndBAreValidForAShiftedLine()
        {
            PolynomialEstimator solver = new PolynomialEstimator();

            double[] xs = new double[] { 1.1, Math.E, Math.PI, 9 };                        

            double expectedA = 7.47;
            double expectedB = -5.5;

            for (int i = 0; i < xs.Length; ++i) 
            {
                solver.Add(xs[i], expectedA + xs[i] * expectedB);
            }

            FastTuple<double, double> outFit;

            bool result = solver.FitALine(out outFit);

            const double Epsilon = 0.000000001;

            Assert.IsTrue(result);
            Assert.IsTrue(Math.Abs(expectedA - outFit.Item1) < Epsilon);
            Assert.IsTrue(Math.Abs(expectedB - outFit.Item2) < Epsilon);
        }

        /// <summary>
        /// Test if Parabola Fitting Is Correct When Function Is A Parabola
        /// </summary>
        [Priority(0)]
        [TestMethod]
        [TestCategory("Unit")]
        public void ParabolaFittingIsCorrectWhenFunctionIsAParabola()
        {
            double[] xs = new double[] { -3.3, -Math.PI, -2, 0.01, 0.25, 0.5, 0.75, 1, 8 };

            FastTuple<double, double, double>[] coefficients = new FastTuple<double, double, double>[]
            {
                new FastTuple<double, double, double>(1.0052, 0.8641, 0.8437),
                new FastTuple<double, double, double>(-2.3, -12000, -0.8437),
                new FastTuple<double, double, double>(1.0052, 0, 0.8437),
                new FastTuple<double, double, double>(7, 0, 0),
                new FastTuple<double, double, double>(0, 0, 20),
                new FastTuple<double, double, double>(0, 3.3, 8),
                new FastTuple<double, double, double>(0, 3.3, 0)
            };

            for (int i = 0; i < coefficients.Length; ++i)
            {
                PolynomialEstimator solver = new PolynomialEstimator();

                for (int j = 0; j < xs.Length; ++j)
                {
                    solver.Add(xs[j], PolynomialEstimator.Estimate(coefficients[i], xs[j]));
                }

                FastTuple<double, double, double> outFit;

                bool result = solver.FitAParabola(out outFit);

                const double Epsilon = 0.000000001;

                Assert.IsTrue(result);
                Assert.IsTrue(Math.Abs(coefficients[i].Item1 - outFit.Item1) < Epsilon);
                Assert.IsTrue(Math.Abs(coefficients[i].Item2 - outFit.Item2) < Epsilon);
                Assert.IsTrue(Math.Abs(coefficients[i].Item3 - outFit.Item3) < Epsilon);
            }
        }

        /// <summary>
        /// Test if Line Fitting Is Correct When Function Is A Line
        /// </summary>
        [Priority(0)]
        [TestMethod]
        [TestCategory("Unit")]
        public void LineFittingIsCorrectWhenFunctionIsALine()
        {
            double[] xs = new double[] { -3.3, -Math.PI, -2, 0.01, 0.25, 0.5, 0.75, 1, 8 };

            FastTuple<double, double>[] coefficients = new FastTuple<double, double>[]
            {
                new FastTuple<double, double>(1.0052, 0.8641),
                new FastTuple<double, double>(2.3, -12000),
                new FastTuple<double, double>(1.0052, 0),
                new FastTuple<double, double>(0, 7)
            };

            for (int i = 0; i < coefficients.Length; ++i)
            {
                PolynomialEstimator solver = new PolynomialEstimator();

                for (int j = 0; j < xs.Length; ++j)
                {
                    solver.Add(xs[j], PolynomialEstimator.Estimate(coefficients[i], xs[j]));
                }

                FastTuple<double, double> outFit;

                bool result = solver.FitALine(out outFit);

                const double Epsilon = 0.000000001;

                Assert.IsTrue(result);
                Assert.IsTrue(Math.Abs(coefficients[i].Item1 - outFit.Item1) < Epsilon);
                Assert.IsTrue(Math.Abs(coefficients[i].Item2 - outFit.Item2) < Epsilon);
            }
        }

        /// <summary>
        /// Test if ParabolaFittingThrowsIfCollectionIsNotInitialized
        /// </summary>
        [Priority(0)]
        [TestMethod]
        [TestCategory("Unit")]
        [ExpectedException(typeof(InvalidOperationException))]
        public void ParabolaFittingThrowsIfCollectionIsNotInitialized()
        {
            PolynomialEstimator solver = new PolynomialEstimator();
            FastTuple<double, double, double> outFit;
            solver.FitAParabola(out outFit);
        }

        /// <summary>
        /// Test if ParabolaFittingReturnsFalseInNotEnoughPoints
        /// </summary>
        [Priority(0)]
        [TestMethod]
        [TestCategory("Unit")]
        public void ParabolaFittingReturnsFalseInNotEnoughPoints()
        {
            PolynomialEstimator solver = new PolynomialEstimator();

            FastTuple<double, double, double> outFit;

            solver.Add(0, 1);
            Assert.IsFalse(solver.FitAParabola(out outFit));

            solver.Add(1, 2);
            Assert.IsFalse(solver.FitAParabola(out outFit));

            solver.Add(2, 3);
            Assert.IsTrue(solver.FitAParabola(out outFit));
        }
    }
}
